﻿#include "QCefWinMsgLoop.h"

//#include <QtCore>

//
#define kMsgHaveWork            (WM_USER+1693)

// Special timer delay placeholder value. Intentionally 32-bit for Windows and
// OS X platform API compatibility.
#define kTimerDelayPlaceholder  (INT_MAX)

// The maximum number of milliseconds we're willing to wait between calls to
// DoWork().
#define kMaxTimerDelay          (30)


//////////////////////////////////////////////////////////////////////////
//
// static
LRESULT CALLBACK QCefWinMsgLoop::WndProc(HWND hwnd,
                                         UINT msg,
                                         WPARAM wparam,
                                         LPARAM lparam)
{
    //QCEF_DEBUG_F();

    /*
    if (msg == kMsgHaveWork)
    {
        QCefWinMsgLoop * msgloop = reinterpret_cast<QCefWinMsgLoop*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
        if (msgloop != nullptr)
        {
            const UINT delay_ms = static_cast<UINT>(lparam);
            msgloop->OnScheduleWork(delay_ms);
        }
    }
    else if (msg == WM_TIMER)
    {
        QCefWinMsgLoop * msgloop = reinterpret_cast<QCefWinMsgLoop*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
        if (msgloop != nullptr)
        {
            msgloop->OnTimerTimeout();
        }
    }

    return DefWindowProc(hwnd, msg, wparam, lparam);
    */

    if (msg == WM_TIMER || msg == kMsgHaveWork)
    {
        QCefWinMsgLoop* msgloop = reinterpret_cast<QCefWinMsgLoop*>(GetWindowLongPtr(hwnd, GWLP_USERDATA));
        if (msgloop != NULL)
        {
            if (msg == kMsgHaveWork)
            {
                const UINT delay_ms = static_cast<UINT>(lparam);
                msgloop->OnScheduleWork(delay_ms);
            }
            else
            {
                msgloop->OnTimerTimeout();
            }
        }
    }

    return DefWindowProc(hwnd, msg, wparam, lparam);
}

//////////////////////////////////////////////////////////////////////////
//
QCefWinMsgLoop* QCefWinMsgLoop::ms_instance = nullptr;

//////////////////////////////////////////////////////////////////////////
//
QCefWinMsgLoop::QCefWinMsgLoop()
    : m_bSetTimer(false)
    , m_wndMsgLoop(NULL)
    , m_bRunCefMsgLoop(false)
    , m_bReentrancy(false)
{
    QCEF_DEBUG();

    HINSTANCE hInstance = ::GetModuleHandle(NULL);
    const wchar_t* const kClassName = L"QCefWinMsgLoop";

    WNDCLASSEX wcex = {};
    wcex.cbSize = sizeof(WNDCLASSEX);
    wcex.lpfnWndProc = QCefWinMsgLoop::WndProc;
    wcex.hInstance = hInstance;
    wcex.lpszClassName = kClassName;
    ::RegisterClassEx(&wcex);

    // Create the message handling window.
    m_wndMsgLoop = ::CreateWindowW(kClassName,
                                   NULL,
                                   WS_OVERLAPPEDWINDOW,
                                   0,
                                   0,
                                   0,
                                   0,
                                   HWND_MESSAGE,
                                   NULL,
                                   hInstance,
                                   NULL);

    ::SetWindowLongPtr(m_wndMsgLoop, GWLP_USERDATA, reinterpret_cast<LONG_PTR>(this));

    //保存第一个实例
    if (ms_instance == nullptr)
    {
        ms_instance = this;
    }
}

QCefWinMsgLoop::~QCefWinMsgLoop()
{
    QCEF_DEBUG();

    //
    if (ms_instance == this)
    {
        ms_instance = nullptr;
    }

    //
    this->killTimer();

    //
    if (m_wndMsgLoop != NULL)
    {
        ::DestroyWindow(m_wndMsgLoop);
        m_wndMsgLoop = NULL;
    }
}


void QCefWinMsgLoop::Quit()
{
    QCEF_DEBUG();

    this->killTimer();

    //
    if (m_wndMsgLoop != NULL)
    {
        ::DestroyWindow(m_wndMsgLoop);
        m_wndMsgLoop = NULL;
    }
}

void QCefWinMsgLoop::Run()
{
    //QCEF_DEBUG();

    this->killTimer();

    // We need to run the message pump until it is idle. However we don't have
    // that information here so we run the message loop "for a while".
    for (int i = 0; i < 20; i++)
    {
        // Do some work.
        CefDoMessageLoopWork();

        // Sleep to allow the CEF proc to do work.
        ::Sleep(kMaxTimerDelay);
    }
}

void QCefWinMsgLoop::OnScheduleMessagePumpWork(UINT delay_ms)
{
    //QCEF_DEBUG();

    // This method may be called on any thread.
    ::PostMessage(m_wndMsgLoop, kMsgHaveWork, 0, static_cast<LPARAM>(delay_ms));
}

void QCefWinMsgLoop::setTimer(UINT delay_ms)
{
    //QCEF_DEBUG();

    m_bSetTimer = true;
    ::SetTimer(m_wndMsgLoop, 9527, static_cast<UINT>(delay_ms), NULL);
}

void QCefWinMsgLoop::killTimer()
{
    //QCEF_DEBUG();

    if (m_bSetTimer)
    {
        m_bSetTimer = false;

        ::KillTimer(m_wndMsgLoop, 9527);
    }
}


void QCefWinMsgLoop::OnScheduleWork(UINT delay_ms)
{
    //QCEF_DEBUG();

    if (delay_ms == kTimerDelayPlaceholder && this->isSetTimer())
    {
        // Don't set the maximum timer requested from DoWork() if a timer event is
        // currently pending.
        return;
    }

    this->killTimer();

    if (delay_ms <= 0)
    {
        // Execute the work immediately.
        this->DoWork();
    }
    else
    {
        // Never wait longer than the maximum allowed time.
        if (delay_ms > kMaxTimerDelay)
        {
            delay_ms = kMaxTimerDelay;
        }

        // Results in call to OnTimerTimeout() after the specified delay.
        this->setTimer(delay_ms);
    }
}

void QCefWinMsgLoop::OnTimerTimeout()
{
    //QCEF_DEBUG();

    this->killTimer();
    this->DoWork();
}


void QCefWinMsgLoop::DoWork()
{
    //QCEF_DEBUG();

    if (this->PerformMessageLoopWork())
    {
        // Execute the remaining work as soon as possible.
        this->OnScheduleMessagePumpWork(0);
    }
    else if (!this->isSetTimer())
    {
        // Schedule a timer event at the maximum allowed time. This may be dropped
        // in OnScheduleWork() if another timer event is already in-flight.
        this->OnScheduleMessagePumpWork(kTimerDelayPlaceholder);
    }
}


bool QCefWinMsgLoop::PerformMessageLoopWork()
{
    //QCEF_DEBUG();

    if (m_bRunCefMsgLoop)
    {
        // When CefDoMessageLoopWork() is called there may be various callbacks
        // (such as paint and IPC messages) that result in additional calls to this
        // method. If re-entrancy is detected we must repost a request again to the
        // owner thread to ensure that the discarded call is executed in the future.
        m_bReentrancy = true;
        return false;
    }

    m_bReentrancy = false;

    //
    m_bRunCefMsgLoop = true;
    try
    {
        CefDoMessageLoopWork();
    }
    catch (...)
    {
        QCEF_FATAL("QCefWinMsgLoop::PerformMessageLoopWork()->CefDoMessageLoopWork() fatal !");
    }    
    m_bRunCefMsgLoop = false;

    // |reentrancy_detected_| may have changed due to re-entrant calls to this method.
    return m_bReentrancy;
}

